/*
 * CyberOrgManager.java
 *
 * Created on April 28, 2005, 11:52 AM
 */

package aa.core;

import aa.core.ActorMessage;

import java.util.Hashtable;
import java.util.LinkedList;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.lang.Math;


/**
 *
 * @author  xinghuizhao
 */
public class CyberOrgManager {
    
    private SchedulerManager m_smSchedulerManager; //reference to the scheduler manager
    
    private Hashtable m_htActornameThread; //map the actor name to its thread
                                           //the scheduler schedules thread, but actually it is actorname
                                           //which is often used to be a parameter...
    
    private Hashtable m_htActorHost; //map the actor name to the host cyberorg
                                     //we need it when an actor creates a new actor
    
    private LinkedList m_llCyberOrgs; //keep the cyberorgs; it is used when scaling up all time slices
                                      //for cyberorgs (granularity control)
    
    private long m_lLargestSlice; //the largest time slice in the system
    
    private long m_lScheduleSmallest, m_lScheduleLargest; //lower and upper limit of acceptable slice
    
    private Platform m_pPlatform;
    
    
    /** Creates a new instance of CyberOrgManager */
    public CyberOrgManager(long p_lScheduleSmallest, long p_lScheduleLargest) {
        
        m_pPlatform = Platform.getPlatform();
        
        m_htActornameThread = new Hashtable();
        m_htActorHost = new Hashtable();
        
        m_llCyberOrgs = new LinkedList();
        
        m_lScheduleSmallest = p_lScheduleSmallest;
        m_lScheduleLargest = p_lScheduleLargest;
        
        m_lLargestSlice = 0;
        
        m_smSchedulerManager = new SchedulerManager(this);
        m_smSchedulerManager.start();
        
        System.out.println("% CyberOrg Manager started.");
        
        
        
    }
    
    /** 
     * Returns the ticksRate of specific actor <br>
     * called by cyberorg primitives
     * 
     * @param p_anActor The name of the actor which returns resources
     * @return The amountn of resources being returned
     */
    public long returnRes(ActorName p_anActor){
        //System.out.println("return Res in COM");
        Thread threadActor = (Thread)m_htActornameThread.get(p_anActor);
        //System.out.println("get thread");
        
        long resource = m_smSchedulerManager.getTimeSlice(threadActor);
        //System.out.println("resource "+resource);
        return resource;
    }
    
    
    
    /** 
     * Reallocate resource for an actor <br>
     * the actor already exists in the scheduler 
     * @param p_anToAlloc The actor name
     * @param rateAssign The resources being allocated to the given actor
     * @return
     */
    public boolean resAlloc(ActorName p_anToAlloc,long rateAssign){
        
        
        Thread threadToAlloc = (Thread)m_htActornameThread.get(p_anToAlloc);
        
        if (threadToAlloc ==null) threadToAlloc = (Thread)m_htActornameThread.get(p_anToAlloc.toString());
        
        //System.out.println("try to get "+p_anToAlloc+" whose thread is "+m_htActornameThread.get(p_anToAlloc));
        
        //System.out.println("Now the table is : "+m_htActornameThread);
        
        if (rateAssign < m_lScheduleSmallest){
            
            long myfactor = accept(rateAssign);
            
            if (myfactor == 0){ //reject
                System.err.println("The request "+ rateAssign +"ms from "+p_anToAlloc+"is unacceptable.");
                return false;
            }
            else{//accept with scaling up
                m_smSchedulerManager.resAlloc(threadToAlloc,rateAssign*myfactor);
                return true;
            }
        }
        
        else{
            if (rateAssign > m_lScheduleLargest){
                System.err.println("The request "+ rateAssign +"ms from "+p_anToAlloc+"is unacceptable.");
                return false;
            }
            else{
                m_smSchedulerManager.resAlloc(threadToAlloc,rateAssign);
                return true;
            }
        }
        
    }
    
    /**
     *  Called by resAlloc when it is trying to accept small request 
     *  
     * @param p_lRateAssign The small request
     * @return The factor that is used to enlarge the control granularity
     */
    private long accept(long p_lRateAssign){
        if (p_lRateAssign==0) return 0; //reject
        
        long factor = m_lScheduleSmallest/p_lRateAssign;
        if (factor*p_lRateAssign<m_lScheduleSmallest) factor++;
        
        
        if (m_lLargestSlice * factor > m_lScheduleLargest){
            return 0;
        }
        else{
            scaleupCyberOrgs(factor);
            m_smSchedulerManager.scaleup(factor);
            return factor;
        }
    }
    
    /**
     * Register a cyberorg to the current platform
     * @param p_coToBeRegistered The cyberorg being registered
     */
    public void registerCyberOrg(CyberOrg p_coToBeRegistered){
        m_llCyberOrgs.add(p_coToBeRegistered);
    }
    
    /**
     * Removes a cyberorg from the current platform
     * @param p_coToBeDeleted The cyberorg being removed
     */
    public void removeCyberOrg(CyberOrg p_coToBeDeleted){
        m_llCyberOrgs.remove(p_coToBeDeleted);
    }
    
    /**
     * Scale up the granularity control
     * 
     * @param p_lfactor The factor by which the granularity is scaled up
     */
    private void scaleupCyberOrgs(long p_lfactor){
        int num = m_llCyberOrgs.size();
        for (int i=0; i<num; i++){
            CyberOrg cyber = (CyberOrg)m_llCyberOrgs.get(i);
            cyber.getResBack(cyber.getTicks()*(p_lfactor-1), cyber.getTicksRate()*(p_lfactor-1),0);
        }
    }
    
    /** 
     * This method is called by the Platform, when an actor is creating another
     * new actor. <br>
     * After the creation, this method is called in order to put
     * the new actor into the cyberorg that the creator is in, <br>and assign resource
     * to it according to the local resource distribution policy of the specific cyberorg
     * 
     * @param p_anCreator The name of the creator
     * @param p_anNewActor The name of the new actor
     */
    
    public void createActor(ActorName p_anCreator, ActorName p_anNewActor){
        CyberOrg host = (CyberOrg)m_htActorHost.get(p_anCreator);
        registerActorCyb(p_anNewActor, host);
        host.insertActor(p_anNewActor);
        host.resourceAlloc(p_anNewActor);
        
    }
    
    
    /**
     *  The method is called by Platform in "creatActor". <br>
     * when an actor is created, we map the actor name to the thread <br>
     * we may look up this hashtable to get thread according to ActorName,
     * and change resource allocatioin in scheduler which only knows threads.
     * 
     * @param p_anActor The name of the actor being registered
     * @param p_thread The thread representing the actor
     */
    public void registerThread(ActorName p_anActor,Thread p_thread){
        m_htActornameThread.put(p_anActor, p_thread);
        m_htActornameThread.put(p_anActor.toString(),p_thread);
    }
    
    /** 
     * Local Migration
     * 
     * @param p_cybToMigrate The cyberorg which is migrating
     * @param p_anDestFacilitator The facilitator actor of the destination cyberorg
     * @param p_conContract The new contract
     */
    
    public void localMigrate(CyberOrg p_cybToMigrate,ActorName p_anDestFacilitator,Contract p_conContract){
        //find the destination parent, add this cyberorg to its list.
        CyberOrg destination = (CyberOrg)m_htActorHost.get(p_anDestFacilitator);
        destination.addChild(p_cybToMigrate);
        
        //pay the money to destination; the migrating cyberorg is already charged the money 
        //before it calle this method:)
        //the destination gives resource to migrating cyberorg
        destination.getResBack(-p_conContract.getTicks(),-p_conContract.getTicksRate(), p_conContract.getPrice()); 
        
        //start receiving resource, need reallocate resource for every actor?
        
        p_cybToMigrate.resourceAllocForAll();
        
    }
    
    /** 
     * Remote Migration
     * 
     * @param p_cybToMigrate The cyberorg which is migrating
     * @param p_anDestFacilitator The destination (on another CyberOrgs platform)
     * @param p_conContract The new contract
     */
    public void remoteMigrate(CyberOrg p_cybToMigrate,ActorName p_anDestFacilitator,Contract p_conContract){
        
        //get the migrating actors's facilitator actorname (for future use when we recursively migrate its children)
        
        
        
        ActorName migratingFacil = p_cybToMigrate.getFacilitator();
        
        m_htActorHost.put(migratingFacil.toString(),p_cybToMigrate);
        
        //put all info of the migrating cyberorg to an actor message
        
        Object[] args = new Object[3];
        args[0] = p_conContract;
        args[1] = new Long(p_cybToMigrate.getECash());
        args[2] = p_cybToMigrate.getActors();
        
        ActorMessage aMsg = new ActorMessage(m_pPlatform.getActorNameOfPlatform(), 
                                            p_anDestFacilitator, 
                                            "receiveCyberOrg", 
                                            args, 
                                            true);
        
        //send the message to desination cyberorg's facilitator;
        //we need return message here, because only after new cyberorg is created on destination
        //can we send actors there
        
        m_pPlatform.sendMessage(aMsg);
        
        //remove migrating cyberorg's actors from local scheduler
        
        /*LinkedList actorsList = p_cybToMigrate.getActors();
        
        for (int i=0;i<actorsList.size();i++){
            Thread threadActor = (Thread)m_htActornameThread.get(actorsList.get(i));
            m_smSchedulerManager.remove(threadActor);
        }
        
        
        
        //migrate migrating cyberorg's actors, need resource reallocation after arrival
        
        String locationAddress = p_anDestFacilitator.getLocationAddress();
        byte[] baRawIP = convertAddrIP(locationAddress);
        
        InetAddress ia = null;
        
        try {
            ia = InetAddress.getByAddress(baRawIP);
            
        }catch(UnknownHostException uhe){
            
        }
        
        for(int j=0;j<actorsList.size();j++){
            try{
                m_pPlatform.migrateActor((ActorName)actorsList.get(j), ia);
            }catch (MigrateActorException mae){
                
            }
        }
        
        //set the host for facilitator???? because after migration, the host cyberorg is newly created... 
        
        Object[] emptyObja = {};
        ActorMessage changeHost = new ActorMessage(m_pPlatform.getActorNameOfPlatform(),
                                                   (ActorName)actorsList.get(0),
                                                   "setHost",
                                                   emptyObja,
                                                   false);
        
        //migrate sub cyberorgs and their actors 
        LinkedList children = p_cybToMigrate.getChildren();
        
        for (int q=0;q<children.size();q++){
            CyberOrg subCyb = (CyberOrg)children.get(q);
            Contract con = subCyb.getContract();
            remoteMigrate((CyberOrg)children.get(q),migratingFacil,con);
        }*/
        
    }
    
    /** 
     * In remote migration, only after we receive the reply message from the destination,<br>
     * which means the new cyberorg is already created in the destination platform, <br>
     * can we migrate actors and child cyberorgs of the migrating cyberorg
     * 
     * @param p_anFacilitator The facilitator of the migrating cyberorg
     */
    
    public void remoteMigrateChild(ActorName p_anFacilitator){
        CyberOrg toMigrate = (CyberOrg)m_htActorHost.get(p_anFacilitator.getUAN());
        
        //remove migrating cyberorg's actors from local scheduler
        
        LinkedList actorsList = toMigrate.getActors();
        
        for (int i=0;i<actorsList.size();i++){
            //System.out.println("remoteMigrateChild "+actorsList.get(i));
            Thread threadActor = (Thread)m_htActornameThread.get(actorsList.get(i));
            //System.out.println("remoteMigrateChild : what we get :"+threadActor);
            if (threadActor == null) {
                threadActor = (Thread)m_htActornameThread.get(actorsList.get(i).toString());
            }
            m_smSchedulerManager.remove(threadActor);
        }
        
        
        
        //migrate migrating cyberorg's actors, need resource reallocation after arrival
        
        String locationAddress = p_anFacilitator.getLocationAddress();
        byte[] baRawIP = convertAddrIP(locationAddress);
        
        InetAddress ia = null;
        
        try {
            ia = InetAddress.getByAddress(baRawIP);
            
        }catch(UnknownHostException uhe){
            
        }
        
        for(int j=0;j<actorsList.size();j++){
            try{
                m_pPlatform.migrateActor((ActorName)actorsList.get(j), ia);
            }catch (MigrateActorException mae){
                
            }
        }
        
        //set the host for facilitator???? because after migration, the host cyberorg is newly created... 
        
        /*Object[] emptyObja = {};
        ActorMessage changeHost = new ActorMessage(m_pPlatform.getActorNameOfPlatform(),
                                                   (ActorName)actorsList.get(0),
                                                   "setHost",
                                                   emptyObja,
                                                   false);*/
        
        //migrate sub cyberorgs and their actors 
        LinkedList children = toMigrate.getChildren();
        
        for (int q=0;q<children.size();q++){
            CyberOrg subCyb = (CyberOrg)children.get(q);
            Contract con = subCyb.getContract();
            remoteMigrate((CyberOrg)children.get(q),p_anFacilitator,con);
        }
        
        //remove cyberorg from list
        removeCyberOrg(toMigrate);
    }
    
    /** 
     * Concert texture IP address to byte array
     * in order to get the InetAddress
     * 
     * @param p_strAddress The IP address
     * @return The corresponding InetAddress
     */
    private byte[] convertAddrIP(String p_strAddress){
        
        byte[] ba = new byte[4];
        
        int from = 0;
        
        String sub; 
        int num;
        
        
        
        for (int i=0;i<3;i++){
            int index = p_strAddress.indexOf(".", from);
            sub = p_strAddress.substring(from,index);
            
            num = Integer.parseInt(sub);
            ba[i] = (byte)num;
            from = index+1;
        }
        sub = p_strAddress.substring(from);
        num = Integer.parseInt(sub);
        ba[3] = (byte)num;
        
        return ba;
        
    }
    
    /** 
     * Register actor-host. 
     * p_anActor is ActorName if it is created locally,
     * but if it is migrated here through CyberOrg migration,
     * we use String
     * 
     * @param p_anActor Actor name or string of actor name being registered
     * @param p_cybHost The host cyberorg
     */
    public void registerActorCyb(Object p_anActor,CyberOrg p_cybHost){
        //System.out.println("put:..."+p_anActor+p_cybHost);
        m_htActorHost.put(p_anActor,p_cybHost);
        
    }
    
    /**
     *  This method is called in remote CyberOrg Migration, after migrating an actor,<br>
     * we need to insert it to the destination cyberorg, reallocate resource to it
     * through the local scheduler 
     * 
     * @param p_anActor Actor name
     * @param p_thread Actor thread
     */
    public void afterMigrateActor(ActorName p_anActor,Thread p_thread){
        m_htActornameThread.put(p_anActor, p_thread);
        m_htActornameThread.put(p_anActor.toString(),p_thread);
        CyberOrg host = (CyberOrg)m_htActorHost.get(p_anActor.toString());
        registerActorCyb(p_anActor,host);
        //m_htActorHost.remove(p_anActor.toString());
        host.resourceAlloc(p_anActor);
        //update the Actors List of host (when migrating here, the actors list has been transfered first,
        //as one of the parameters, but now we need to map the actual object(ActorName)) 
        host.updateActors(p_anActor);
    }
    
    /**
     * This method is called after remote migration, in order to set host for the migrated 
     * facilitator
     * 
     * @param p_objFacilitator The facilitator actor
     * @return The host cyberorg
     */
    public CyberOrg lookupHost(Object p_objFacilitator){
        return (CyberOrg)m_htActorHost.get(p_objFacilitator); 
    }
    
    /** 
     * Called when an actor is destroyed
     * 
     * @param p_anActor The actor being destroyed
     */
    public void removeActor(ActorName p_anActor){
        Thread threadActor = (Thread)m_htActornameThread.get(p_anActor);
        long timeslice = m_smSchedulerManager.getTimeSlice(threadActor);
        
        
        m_smSchedulerManager.remove(threadActor);
        CyberOrg host = (CyberOrg)m_htActorHost.get(p_anActor);
        host.getResBack(0,timeslice,0);
        host.deleteActor(p_anActor);
        m_htActorHost.remove(p_anActor);
        m_htActornameThread.remove(p_anActor);
    }
    
    /**
     * Called by scheduler after every scheduling cycle, for total time control
     * @return The updated total ticks
     */
    public long updateTotalTicks(){
        int numCybers = m_llCyberOrgs.size();
        long total=0;
        for (int i=0;i<numCybers;i++){
            CyberOrg cyber = (CyberOrg)m_llCyberOrgs.get(i);
            total+=cyber.chargeTotalTicks();
        }
        return total;
    }
    
    
    /**
     * 
     * These method are called when a cyberorg runs out of resource, and when it gets more
     * resources and can be scheduled again.
     * 
     * @param p_cybToActive The cyberorg being reactivated
     */
    public void reactiveCyberOrg(CyberOrg p_cybToActive){
        LinkedList actors = p_cybToActive.getActors();
        int numActors = actors.size();
        
        Thread threadActor;
        
        for (int i=0;i<numActors;i++){
            threadActor = (Thread)m_htActornameThread.get(actors.get(i));
            m_smSchedulerManager.insert(threadActor);
        }
    }
    
    /**
     * Disactivate a cyberorg when it runs out of resources
     * 
     * @param p_cybToDisactive The cyberorg being disactivated
     */
    public void disactiveCyberOrg(CyberOrg p_cybToDisactive){
        LinkedList actors = p_cybToDisactive.getActors();
        int numActors = actors.size();
        
        Thread threadActor;
        
        for (int i=0;i<numActors;i++){
            threadActor = (Thread)m_htActornameThread.get(actors.get(i));
            m_smSchedulerManager.remove(threadActor);
        }
        
    }
    
    /**
     * 
     * This method is called by facilitator actors before migration. 
     * It is used to pick up a best destination cyberorg (according to the price)
     *
     * @return The potential destination cyberorg
     */
    public CyberOrg lookupDestination(){
        CyberOrg destination =(CyberOrg)m_llCyberOrgs.get(0); 
        long lowestPrice = destination.getPrice();
        CyberOrg tmpCyber;
        long tmpPrice;
        for (int i=1;i<m_llCyberOrgs.size();i++){
            tmpCyber =(CyberOrg)m_llCyberOrgs.get(i);
            tmpPrice = tmpCyber.getPrice();
            if (tmpPrice<lowestPrice){
                lowestPrice = tmpPrice;
                destination = tmpCyber;
            }
        }
        return destination;
    }
}
